package cronJob

import (
	"context"
	"fmt"
	"gitee.com/igolang/imoney/internal/common/keys"
	"gitee.com/igolang/imoney/internal/core/client"
	coreCron "gitee.com/igolang/imoney/internal/core/cron"
	"gitee.com/igolang/imoney/internal/core/logger"
	"gitee.com/igolang/pkg/xcron"
	"gitee.com/igolang/pkg/xredis"
	"gitee.com/igolang/pkg/xtime"
	"github.com/pkg/errors"
	"sync"
	"time"
)

var jobs sync.Map

type Job struct {
	Key        string `json:"key"`
	Name       string `json:"name"`
	Desc       string `json:"desc"`
	Spec       string `json:"spec"`
	Concurrent bool   `json:"concurrent"` // 是否允许并发
	result     *Result
	cmd        func() error
	callback   func(result *Result)
}

type Result struct {
	Key       string     `json:"key"`
	Name      string     `json:"name"`
	Desc      string     `json:"desc"`
	Spec      string     `json:"spec"`
	Err       error      `json:"err"`
	Exception any        `json:"exception"`
	StartTime xtime.Time `json:"startTime"`
	EndTime   xtime.Time `json:"endTime"`
}

func (j *Job) Run() {
	logger.Log().Info("job start", "job", j)
	j.result = &Result{
		Key:       j.Key,
		Name:      j.Name,
		Desc:      j.Desc,
		Spec:      j.Spec,
		StartTime: xtime.Time(time.Now()),
	}
	defer func() {
		j.saveResult()
		logger.Log().Info("job end", "job", j)
	}()
	if j.cmd == nil {
		j.result.Err = xcron.ErrCmdFuncNil
		return
	}
	// 判断是否要加锁
	rc := client.Redis()
	ctx := context.TODO()
	if !j.Concurrent { // 不允许并发
		locker := xredis.NewRedisLocker(rc, "job:%s:locker", j.Key)
		locker.WithConfig(3, 10*time.Millisecond, 24*time.Hour) // 重试次数及锁定期
		if !locker.Lock(ctx, 1) {
			j.result.Err = errors.New("当前任务执行中")
			return
		}
		defer locker.Unlock(ctx, 1)
	}
	statusKey := keys.JobStatus(j.Key)
	rc.Set(ctx, statusKey.Key, 1, statusKey.Expire)
	defer func() {
		rc.Del(ctx, statusKey.Key)
	}()
	j.cmdRun()
}

func (j *Job) cmdRun() {
	defer j.cmdRecover()
	j.result.Err = j.cmd()
}

func (j *Job) cmdRecover() {
	if err := recover(); err != nil {
		j.result.Exception = errors.Errorf("%v", err)
	}
}

func (j *Job) saveResult() {
	j.result.EndTime = xtime.Time(time.Now())
	if j.callback != nil {
		j.callback(j.result)
	} else {
		logger.Log().Info("job callback nil", "job", j)
	}
	logger.Log().Info("job result", "job", j, "result", j.result)
	if j.result.Err != nil {
		logger.Log().Error("job result err", "job", j, "err", fmt.Sprintf("%+v", j.result.Err))
	}
	if j.result.Exception != nil {
		logger.Log().Error("job result exception", "job", j, "exception", fmt.Sprintf("%+v", j.result.Exception))
	}
}

func (j *Job) SetCmd(cmd func() error) {
	j.cmd = cmd
}

func (j *Job) SetCallback(callback func(result *Result)) {
	j.callback = callback
}

// Register 注册任务
// 若任务已存在，将会覆盖
func Register(key string, cmd func() error) error {
	if key == "" {
		return xcron.ErrKeyNil
	}
	if cmd == nil {
		return xcron.ErrCmdFuncNil
	}
	jobs.Store(key, cmd)
	return nil
}

func Load(key string) func() error {
	value, ok := jobs.Load(key)
	if !ok {
		return nil
	}

	f, ok := value.(func() error)
	if !ok {
		return nil
	}
	return f
}

func AddScheduler(job *Job) error {
	coreCron.Remove(job.Key)
	if job.cmd == nil {
		return xcron.ErrCmdFuncNil
	}
	_, err := coreCron.AddFunc(
		job.Spec,
		job.Run,
		job.Key,
		job.Name,
		job.Desc,
	)
	return err
}

func RemoveScheduler(key string) {
	coreCron.Remove(key)
}

func Running(key string) bool {
	statusKey := keys.JobStatus(key)
	return client.Redis().Exists(context.TODO(), statusKey.Key).Val() > 0
}
